/**
 * 
 */
package model;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Observable;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.Semaphore;

/**
 * @author Florian.B Rachid.D
 */
public class Environnement extends Observable implements Runnable, Serializable {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	/**
	 * Map représentant les cellules en vie au temps t.
	 */
	private HashMap<Coordonnee, Cellule> mapTps1;
	/**
	 * La largeur de l'environnement.
	 */
	private int largeur = -1;
	/**
	 * La hauteur de l'environnement.
	 */
	private int hauteur = -1;
	/**
	 * Dépend de la forme de la cellule : carre 8 voisines, triangle 6 voisines.
	 */
	private int nbVoisine = 8;

	private Semaphore sem = new Semaphore(1);

	private int nbthread = 2;

	public Environnement(int hauteur, int largeur) {
		mapTps1 = new HashMap<Coordonnee, Cellule>();
		this.largeur = largeur;
		this.hauteur = hauteur;
	}

	public void genese() {
		/**
		 * Création aléatoire des cellules.
		 */
		Random randBool = new Random();
		for (int hauteur = 0; hauteur < this.hauteur; hauteur++) {
			for (int largeur = 0; largeur < this.largeur; largeur++) {
				if (randBool.nextBoolean() == true) {

					Coordonnee coord = new Coordonnee(hauteur, largeur,
							this.hauteur, this.largeur);
					Cellule cel = new Cellule(coord);
					try {
						sem.acquire();
						mapTps1.put(coord, cel);
						sem.release();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}
		this.setChanged();
		this.notifyObservers();
	}

	public void reinitialiser(int hauteur, int largeur) {
		try {
			sem.acquire();
			this.mapTps1.clear();
			this.largeur = largeur;
			this.hauteur = hauteur;
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.setChanged();
		this.notifyObservers();
	}

	@SuppressWarnings("unchecked")
	synchronized public void traitement() {
		if (sem.tryAcquire()) {
			/**
			 * Map représentant les cellules en vie au temps t + 1;
			 */
			HashMap<Coordonnee, Cellule> mapTps2 = new HashMap<Coordonnee, Cellule>();
			/**
			 * 
			 * Map représentant la valeur du compteur de génération de vie. Ce
			 * compteur représente le nombre de cellule voisine vivante. Si le
			 * nombre est 3 alors une nouvelle cellule est créée.
			 */
			HashMap<Coordonnee, Integer> mapDonneVie = new HashMap<Coordonnee, Integer>();

			Collection<Cellule> colTps1 = mapTps1.values();
			Thread[] t = new Thread[nbthread];
			Object[] ar = colTps1.toArray();
			int pas;
			int debut;
			int fin;

			if (ar.length > nbthread && nbthread > 1) {
				pas = (ar.length) / nbthread;
				debut = 0;
				fin = pas;
				HashMap<Coordonnee, Integer> [] mapV = new HashMap[nbthread];
				for (int i = 0; i < nbthread; i++) {
					mapV[i] = new HashMap<Coordonnee, Integer>();
					t[i] = new Thread(new TraitementThreadVie(ar, mapTps1, mapTps2,
							mapV[i], debut, fin, nbVoisine));
					t[i].start();
					debut = fin + 1;
					if (i == nbthread - 2)
						fin = ar.length - 1;
					else
						fin = fin + pas;
				}
				for (int i = 0; i < nbthread; i++)
					try {
						t[i].join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				/* Assemblage des mapV dans la mapDonneVie */
				mapDonneVie = (HashMap<Coordonnee, Integer>) mapV[0].clone();
				for(int i = 1; i < nbthread; i++)
				{
					Set<Coordonnee> coords = mapV[i].keySet();
					Iterator<Coordonnee> cos = coords.iterator();
					
					while (cos.hasNext()) {
						Coordonnee c = cos.next();
						if(mapDonneVie.containsKey(c))
						{
							Integer j = mapDonneVie.get(c) + mapV[i].get(c);
							mapDonneVie.put(c, j);
						}
						else
						{
							mapDonneVie.put(c, mapV[i].get(c));
						}
					}
				}
			} else {
				for (int i = 0; i < ar.length; i++) {
					Cellule c = (Cellule) ar[i];
					c.traitement(mapTps1, mapTps2, mapDonneVie, nbVoisine);
				}
			}
			/**
			 * Création des cellules.
			 */
			if (Rules.giveLife()) {
				Set<Coordonnee> coords = mapDonneVie.keySet();
				Object [] coord = coords.toArray();
				pas = (coord.length) / nbthread;
				debut = 0;
				fin = pas - 1;
				for (int i = 0; i < nbthread; i++) {
					t[i] = new Thread(
							new TraitementThreadDonneVie(mapTps2, mapDonneVie, coord,
									debut, fin));
					t[i].start();
					debut = fin + 1;
					if (i == nbthread - 2) {
						fin = coord.length - 1;
					} else {
						fin = fin + pas;
					}
				}
				for (int i = 0; i < nbthread; i++) {
					try {
						t[i].join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			mapTps1.clear();
			mapTps1 = mapTps2;
			mapDonneVie = null;
			sem.release();
			this.setChanged();
			this.notifyObservers();
		}
	}

	@Override
	public String toString() {
		String s = "";
		try {
			sem.acquire();
			for (int hauteur = 0; hauteur < this.hauteur; hauteur++) {
				for (int largeur = 0; largeur < this.largeur; largeur++) {
					if (mapTps1.get(new Coordonnee(hauteur, largeur,
							this.hauteur, this.largeur)) == null) {
						s += "~";
					} else {
						s += "#";
					}
				}
				s += "\n";
			}
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return s;
	}

	public int getNbVivant() {
		int i = 0;
		try {
			sem.acquire();
			i = mapTps1.size();
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return i;
	}

	public void tuerCellule(Coordonnee coord) {
		try {
			sem.acquire();
			mapTps1.remove(coord);
			sem.release();
			this.setChanged();
			this.notifyObservers();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void vieCellule(Coordonnee coord) {
		try {
			sem.acquire();
			mapTps1.put(coord, new Cellule(coord));
			sem.release();
			this.setChanged();
			this.notifyObservers();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void toggleCellule(Coordonnee coord) {
		Boolean b;
		try {
			sem.acquire();
			b = mapTps1.containsKey(coord);
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
			return;
		}
		if (b)
			tuerCellule(coord);
		else
			vieCellule(coord);
	}

	/**
	 * @return the largeur
	 */
	public int getLargeur() {
		return largeur;
	}

	/**
	 * @return the hauteur
	 */
	public int getHauteur() {
		return hauteur;
	}

	@Override
	public void run() {
		this.traitement();
	}

	public Cellule getCellule(Coordonnee coordonnee) {
		Cellule cel = null;
		try {
			sem.acquire();
			cel = mapTps1.get(coordonnee);
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return cel;
	}

	public Object getMapCellule() {
		return mapTps1;
	}

	public void loadMap(HashMap<Coordonnee, Cellule> hashMap, int readInt,
			int readInt2) {
		try {
			sem.acquire();
			mapTps1.clear();
			mapTps1.putAll(hashMap);
			this.hauteur = readInt;
			this.largeur = readInt2;
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

	public int getNbThread() {
		return nbthread;
	}

	public void setNbThread(int nbthread) {
		try {
			sem.acquire();
			this.nbthread = nbthread;
			sem.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	public void addTemplate(HashMap<Coordonnee, Cellule> map, Coordonnee coord) {
		try {
			sem.acquire();
			Set<Coordonnee> coords = map.keySet();
			Iterator<Coordonnee> cos = coords.iterator();
			while (cos.hasNext()) {
				Coordonnee c = cos.next().relative(coord);
				if (c.isValid())
					mapTps1.put(c, new Cellule(c));
			}
			sem.release();
			this.setChanged();
			this.notifyObservers();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/*
	 * // static public void main(String arg[]) { // Environnement env = new
	 * Environnement(3, 6); // ThreadEnvironnement t = new
	 * ThreadEnvironnement(env); // Observer obj = new Observer() { // //
	 * @Override // public void update(Observable o, Object arg) { // // if
	 * (o.getClass() == Environnement.class) { // Environnement env =
	 * (Environnement) o; // System.out.println("Environnement :"); //
	 * System.out.println(env); // } // } // }; // env.addObserver(obj); //
	 * env.genese(); // t.setEnExecution(true); // t.start(); // }
	 */
}
